/*
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections.Generic;

using Gtk;
using Mono.Unix;

using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;

namespace DCSharp.Gui
{
	/// <summary>
	/// A PageManager that presents pages in a tree.
	/// </summary>
	public class PageManagerTree : PageManager
	{
		private IFavoriteHubManager favoriteHubManager;

		private HPaned hpaned;
		private MenuTreeView treeView;
		private TreeStore store;

		private TreeIter hubIter;
		private TreeIter userIter;
		private TreeIter listIter;

		private List<Page> pages;
		private Dictionary<Page, TreeIter> pageToIter;
		private Page currentPage;

		private static List<Type> sortOrder;

		#region Constructors

		static PageManagerTree()
		{
			sortOrder = new List<Type>();
			sortOrder.Add(typeof(SearchPage));
			sortOrder.Add(typeof(DownloadPage));
			sortOrder.Add(typeof(UploadPage));
		}

		public PageManagerTree(IFavoriteHubManager favoriteHubManager) : base()
		{
			if (favoriteHubManager == null)
			{
				throw new ArgumentNullException("favoriteHubManager");
			}
			this.favoriteHubManager = favoriteHubManager;

			BorderWidth = 6;

			pages = new List<Page>();
			pageToIter = new Dictionary<Page, TreeIter>();

			// User interface
			hpaned = new HPaned();
			hpaned.Show();

			Add(hpaned);

			ScrolledWindow sw = new ScrolledWindow();
			sw.ShadowType = ShadowType.In;
			sw.Show();

			hpaned.Pack1(sw, false, false);

			// TreeView
			store = new TreeStore(typeof(Page), typeof(string), typeof(bool));
			store.SetSortFunc(0, PageCompareFunc);
			store.SetSortColumnId(0, SortType.Ascending);

			treeView = new MenuTreeView(store);
			treeView.HeadersVisible = false;
			treeView.PopulatePopup += OnPouplatePopup;
			treeView.Selection.Mode = SelectionMode.Browse;
			treeView.Selection.Changed += OnSelectionChanged;
			treeView.Selection.SelectFunction = TreeSelectionFunc;
			treeView.SetSizeRequest(140, 0);
#if GTK_2_10
			treeView.ShowExpanders = false;
#endif
			treeView.Show();

			sw.Add(treeView);

			// Columns
			TreeViewColumn column;
			CellRendererPixbuf pixbufRenderer;
			CellRendererText textRenderer;

			// Page
			column = new TreeViewColumn();
			column.Title = Catalog.GetString("Page");
			column.Expand = true;
			column.SortColumnId = 0;
			column.Spacing = 3;
			treeView.AppendColumn(column);

			pixbufRenderer = new CellRendererPixbuf();
			column.PackStart(pixbufRenderer , false);
			column.SetCellDataFunc(pixbufRenderer, PixbufDataHandler);

			textRenderer = new CellRendererText();
			textRenderer.Ellipsize = Pango.EllipsizeMode.End;
			column.PackStart(textRenderer , true);
			column.SetCellDataFunc(textRenderer, TextDataHandler);

			// Events
			favoriteHubManager.HubsSorted += OnFavoriteHubsSorted;
		}

		#endregion

		#region Properties

		public override int Count
		{
			get { return pages.Count; }
		}

		public override Page Current
		{
			get { return currentPage; }
		}

		public override Page this[int index]
		{
			get { return pages[index]; }
		}

		#endregion

		#region Methods

		public override void Add(Page page)
		{
			TreeIter iter;
			TreeIter parent = GetPageParent(page);

			// Add the page to the store
			if (!parent.Equals(TreeIter.Zero))
			{
				iter = store.AppendValues(parent, page);

				TreePath path = store.GetPath(parent);
				treeView.ExpandToPath(path);
			}
			else
			{
				iter = store.AppendValues(page);
			}
			pageToIter.Add(page, iter);
			pages.Add(page);

			page.PageChanged += OnPageChanged;

			if (currentPage == null)
			{
				Present(page);
			}
			OnPageAdded(page);
		}

		public override void Remove(Page page)
		{
			TreeIter iter;
			if (!pages.Contains(page) || !pageToIter.TryGetValue(page, out iter))
			{
				return;
			}

			if (currentPage == page)
			{
				hpaned.Remove(page);
				currentPage = null;
			}
			TreePath path = store.GetPath(iter);

			// Remove from the store
			TreeIter parent;
			if (store.IterParent(out parent, iter))
			{
				store.Remove(ref iter);
				RemoveIfEmpty(ref parent);
			}
			else
			{
				store.Remove(ref iter);
			}

			// Selection handling
			if (!store.IterIsValid(iter))
			{
				SelectPathOrPrev(path);
			}
			else
			{
				SelectIterOrChild(iter);
			}

			// Remove
			pageToIter.Remove(page);
			pages.Remove(page);

			page.PageChanged -= OnPageChanged;

			OnPageRemoved(page);

			if (Count == 0)
			{
				OnCurrentPageChanged(null);
			}
		}

		public override bool Contains(Page page)
		{
			return pages.Contains(page);
		}

		public override void Present(Page page)
		{
			TreeIter iter;
			if (pageToIter.TryGetValue(page, out iter))
			{
				treeView.Selection.SelectIter(iter);
			}
		}

		protected override void OnDestroyed()
		{
			favoriteHubManager.HubsSorted -= OnFavoriteHubsSorted;
			base.OnDestroyed();
		}

		private TreeIter GetPageParent(Page page)
		{
			if (page is HubPage)
			{
				if (!store.IterIsValid(hubIter))
				{
					hubIter = store.AppendValues(null, Catalog.GetString("Hubs"));
				}
				return hubIter;
			}
			else if (page is ChatPage)
			{
				if (!store.IterIsValid(userIter))
				{
					userIter = store.AppendValues(null, Catalog.GetString("Conversations"));
				}
				return userIter;
			}
			else if (page is FileListPage)
			{
				if (!store.IterIsValid(listIter))
				{
					listIter = store.AppendValues(null, Catalog.GetString("File Lists"));
				}
				return listIter;
			}
			return TreeIter.Zero;
		}

		private void RemoveIfEmpty(ref TreeIter iter)
		{
			if (store.IterIsValid(iter) && !store.IterHasChild(iter))
			{
				store.Remove(ref iter);
				iter = TreeIter.Zero;
			}
		}

		private void SelectPathOrPrev(TreePath path)
		{
			do
			{
				do
				{
					TreeIter iter;
					if (store.GetIter(out iter, path))
					{
						SelectIterOrChild(iter);
						return;
					}
				}
				while (path.Prev());
			}
			while (path.Up() && path.Depth > 0);
		}

		private void SelectIterOrChild(TreeIter iter)
		{
			TreeIter childIter;
			if (store.IterChildren(out childIter, iter))
			{
				treeView.Selection.SelectIter(childIter);
			}
			else
			{
				treeView.Selection.SelectIter(iter);
			}
		}

		private void SetImportant(Page page, bool important)
		{
			TreeIter iter = pageToIter[page];
			store.SetValue(iter, 2, important);

			TreeIter parent;
			if (store.IterParent(out parent, iter))
			{
				UpdateIterImportant(parent);
			}
		}

		private void UpdateIterImportant(TreeIter parent)
		{
			bool important = false;

			TreeIter iter;
			if (store.IterChildren(out iter, parent))
			{
				do
				{
					if ((bool)store.GetValue(iter, 2))
					{
						important = true;
						break;
					}
				}
				while (store.IterNext(ref iter));
			}
			store.SetValue(parent, 2, important);
		}

		private bool TreeSelectionFunc(TreeSelection selection, TreeModel model,
			TreePath path, bool pathCurrentlySelected)
		{
			TreeIter iter;
			if (model.GetIter(out iter, path))
			{
				return !model.IterHasChild(iter);
			}
			return true;
		}

		#region Tree Sorting 

		private int PageCompareFunc(TreeModel model, TreeIter iterA,
			TreeIter iterB)
		{
			Page pageA = (Page)model.GetValue(iterA, 0);
			Page pageB = (Page)model.GetValue(iterB, 0);

			Type typeA = pageA != null ? pageA.GetType() : null;
			Type typeB = pageB != null ? pageB.GetType() : null;

			int indexA = sortOrder.IndexOf(typeA);
			int indexB = sortOrder.IndexOf(typeB);

			if (indexA != -1 && indexB != -1)
			{
				return indexA - indexB;
			}
			else if (indexA != -1 && indexB == -1)
			{
				return -1;
			}
			else if (indexA == -1 && indexB != -1)
			{
				return 1;
			}

			// Put the hubs in the same order as in the hub window
			HubPage hubPageA = pageA as HubPage;
			HubPage hubPageB = pageB as HubPage;
			if (hubPageA != null && hubPageB != null)
			{
				return CompareHubPages(hubPageA, hubPageB);
			}

			// Fallback to sorting by title
			string titleA = pageA != null ? pageA.Title :
				(string)model.GetValue(iterA, 1);

			string titleB = pageB != null ? pageB.Title :
				(string)model.GetValue(iterB, 1);

			return String.Compare(titleA, titleB); 
		}

		private int CompareHubPages(HubPage pageA, HubPage pageB)
		{
			FavoriteHubInfo hubInfoA = GetFavoriteHubInfo(pageA);
			FavoriteHubInfo hubInfoB = GetFavoriteHubInfo(pageB);

			List<FavoriteHubInfo> hubInfos = new List<FavoriteHubInfo>(
				favoriteHubManager.Hubs);

			int posA = hubInfos.IndexOf(hubInfoA);
			int posB = hubInfos.IndexOf(hubInfoB);

			// One page is not in the list, put it at the bottom
			if (posA == -1 && posB >= 0)
			{
				return 1;
			}
			else if (posA >= 0 && posB == -1)
			{
				return -1;
			}
			// No hub found in the list. Sort by title
			else if (posA == -1 && posB == -1)
			{
				return String.Compare(pageA.Title, pageB.Title);
			}
			// Both hubs found in the list
			return posA - posB;
		}

		private FavoriteHubInfo GetFavoriteHubInfo(HubPage hubPage)
		{
			foreach (FavoriteHubInfo hubInfo in favoriteHubManager.Hubs)
			{
				Uri uri;
				if (hubInfo.Hostname != null &&
					StringUtil.TryCreateUri(hubInfo.Hostname, out uri) &&
					uri == hubPage.Hub.Uri)
				{
					return hubInfo;
				}
			}
			return null;
		}

		#endregion

		private void PixbufDataHandler(TreeViewColumn column,
			CellRenderer renderer, TreeModel model, TreeIter iter)
		{
			CellRendererPixbuf pixbufRenderer = (CellRendererPixbuf)renderer;
			Page page = (Page)model.GetValue(iter, 0);

			pixbufRenderer.Pixbuf = page != null ? page.Icon : null;
			pixbufRenderer.Visible = page != null;
		}

		private void TextDataHandler(TreeViewColumn column, CellRenderer renderer,
			TreeModel model, TreeIter iter)
		{
			CellRendererText textRenderer = (CellRendererText)renderer;

			Page page = (Page)model.GetValue(iter, 0);
			string title = page != null ? page.Title :
				(string)model.GetValue(iter, 1);

			bool isGroup = model.IterHasChild(iter);
			bool important = (bool)model.GetValue(iter, 2);

			// Update
			if (isGroup)
			{
				Style style = Style;
				Gdk.Color color = style.TextColors[(int)StateType.Insensitive];

				color.Red = (ushort)((color.Red + style.White.Red) / 2);
				color.Green = (ushort)((color.Green + (ushort)style.White.Green) / 2);
				color.Blue = (ushort)((color.Blue + style.White.Blue) / 2);

				textRenderer.CellBackgroundGdk = color;
			}
			else
			{
				textRenderer.CellBackground = null;
			}

			if (important)
			{
				textRenderer.Markup = String.Format("<b>{0}</b>",
					GLib.Markup.EscapeText(title));
			}
			else
			{
				textRenderer.Text = title;
			}
		}

		private void OnPageChanged(object obj, EventArgs args)
		{
			Page page = (Page)obj;
			if (page != currentPage)
			{
				SetImportant(page, true);
			}
			QueueDraw();
		}

		private void OnPouplatePopup(object obj, PopulatePopupArgs args)
		{
			Page page = null;
			TreeIter iter;

			Gdk.EventButton evnt = Global.CurrentEvent as Gdk.EventButton;
			if (evnt != null)
			{
				TreePath path;
				if (treeView.GetPathAtPos((int)evnt.X, (int)evnt.Y, out path) &&
					store.GetIter(out iter, path))
				{
					page = (Page)store.GetValue(iter, 0);
				}
			}
			else if (treeView.Selection.GetSelected(out iter))
			{
				page = (Page)store.GetValue(iter, 0);
			}

			if (page != null)
			{
				MenuItem item = new ImageMenuItem(Stock.Close, null);
				item.Activated += delegate
				{
					Remove(page);
				};
				args.Menu.Append(item);
				item.Show();
			}
		}

		private void OnSelectionChanged(object obj, EventArgs args)
		{
			TreeIter iter;
			if (treeView.Selection.GetSelected(out iter))
			{
				Page page = (Page)store.GetValue(iter, 0);
				if (page == currentPage || page == null)
				{
					return;
				}
				else if (currentPage != null)
				{
					hpaned.Remove(currentPage);
				}

				page.BorderWidth = 0;
				hpaned.Pack2(page, true, true);
				page.Show();

				currentPage = page;
				SetImportant(page, false);

				OnCurrentPageChanged(page);
			}
		}

		private void OnFavoriteHubsSorted(object obj, EventArgs args)
		{
			Application.Invoke(delegate
			{
				// Re-sort the tree
				store.SetSortColumnId(0, SortType.Descending);
				store.SetSortColumnId(0, SortType.Ascending);
			});
		}

		#endregion
	}
}
